using System.Collections.Generic;
using MirrorMirror.Tests.Support;
using NUnit.Framework;

namespace MirrorMirror.Tests
{
    [TestFixture]
    public class ReflectionTests {
        #region Setup/Teardown

        [SetUp]
        public void SetUp() {
            instance = new TestClass
                           {
                               InterfaceProperty = new List<int>(),
                               ValueProperty = 3,
                               NullableProperty = 3,
                               ObjectProperty = "bla",
                               ClassProperty = "bla",
                               Chain = new ChainClass
                                            {
                                                ChainProperty = "wiblo"
                                            }
                           };
            emptyInstance = new TestClass();
        }

        #endregion

        private TestClass instance;
        private TestClass emptyInstance;

        [Test]
        public void ShouldGetClassFields() {
            instance.Get("classField").Should(Be.EqualTo(instance.ClassProperty));
        }

        [Test]
        public void ShouldGetClassProperties() {
            instance.Get("ClassProperty").Should(Be.EqualTo(instance.ClassProperty));
        }

        [Test]
        public void ShouldGetInterfaceFields() {
            instance.Get("interfaceField").Should(Be.EqualTo(instance.InterfaceProperty));
        }

        [Test]
        public void ShouldGetInterfaceProperties() {
            instance.Get("InterfaceProperty").Should(Be.EqualTo(instance.InterfaceProperty));
        }

        [Test]
        public void ShouldGetNullableFields() {
            instance.Get("nullableField").Should(Be.EqualTo(instance.NullableProperty));
        }

        [Test]
        public void ShouldGetNullableProperties() {
            instance.Get("NullableProperty").Should(Be.EqualTo(instance.NullableProperty));
        }

        [Test]
        public void ShouldGetNullClassFields() {
            emptyInstance.Get("classField").Should(Be.EqualTo(emptyInstance.ClassProperty));
        }

        [Test]
        public void ShouldGetNullClassProperties() {
            emptyInstance.Get("ClassProperty").Should(Be.EqualTo(emptyInstance.ClassProperty));
        }

        [Test]
        public void ShouldGetNullInterfaceFields() {
            emptyInstance.Get("interfaceField").Should(Be.EqualTo(emptyInstance.InterfaceProperty));
        }

        [Test]
        public void ShouldGetNullInterfaceProperties() {
            emptyInstance.Get("InterfaceProperty").Should(Be.EqualTo(emptyInstance.InterfaceProperty));
        }

        [Test]
        public void ShouldGetNullNullableFields() {
            emptyInstance.Get("nullableField").Should(Be.EqualTo(emptyInstance.NullableProperty));
        }

        [Test]
        public void ShouldGetNullNullableProperties() {
            emptyInstance.Get("NullableProperty").Should(Be.EqualTo(emptyInstance.NullableProperty));
        }

        [Test]
        public void ShouldGetNullObjectFields() {
            emptyInstance.Get("objectField").Should(Be.EqualTo(emptyInstance.ObjectProperty));
        }

        [Test]
        public void ShouldGetNullObjectProperties() {
            emptyInstance.Get("ObjectProperty").Should(Be.EqualTo(emptyInstance.ObjectProperty));
        }

        [Test]
        public void ShouldGetObjectFields() {
            instance.Get("objectField").Should(Be.EqualTo(instance.ObjectProperty));
        }

        [Test]
        public void ShouldGetObjectProperties() {
            instance.Get("ObjectProperty").Should(Be.EqualTo(instance.ObjectProperty));
        }

        [Test]
        public void ShouldGetValueFields() {
            instance.Get("valueField").Should(Be.EqualTo(instance.ValueProperty));
        }

        [Test]
        public void ShouldGetValueProperties() {
            instance.Get("ValueProperty").Should(Be.EqualTo(instance.ValueProperty));
        }

        [Test]
        public void ShouldSetClassProperties() {
            emptyInstance.Set("ClassProperty", "lala");
            emptyInstance.ClassProperty.Should(Be.EqualTo("lala"));
        }

        [Test]
        public void ShouldSetInterfaceProperties() {
            var value = new List<int>();
            emptyInstance.Set("InterfaceProperty", value);
            emptyInstance.InterfaceProperty.Should(Be.EqualTo(value));
        }

        [Test]
        public void ShouldSetNullableProperties() {
            emptyInstance.Set("NullableProperty", 3);
            emptyInstance.NullableProperty.Should(Be.EqualTo(3));
        }

        [Test]
        public void ShouldSetNullClassProperties() {
            emptyInstance.Set("ClassProperty", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullInterfaceProperties() {
            emptyInstance.Set("InterfaceProperty", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullNullableProperties() {
            emptyInstance.Set("NullableProperty", null);
            emptyInstance.NullableProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullObjectProperties() {
            emptyInstance.Set("ObjectProperty", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetObjectProperties() {
            var value = new List<int>();
            emptyInstance.Set("ObjectProperty", value);
            emptyInstance.ObjectProperty.Should(Be.EqualTo(value));
        }

        [Test]
        public void ShouldSetValueProperties() {
            emptyInstance.Set("ValueProperty", 1);
            emptyInstance.ValueProperty.Should(Be.EqualTo(1));
        }
        
        [Test]
        public void ShouldSetClassFields()
        {
            emptyInstance.Set("classField", "lala");
            emptyInstance.ClassProperty.Should(Be.EqualTo("lala"));
        }

        [Test]
        public void ShouldSetInterfaceFields()
        {
            var value = new List<int>();
            emptyInstance.Set("interfaceField", value);
            emptyInstance.InterfaceProperty.Should(Be.EqualTo(value));
        }

        [Test]
        public void ShouldSetNullableFields()
        {
            emptyInstance.Set("nullableField", 3);
            emptyInstance.NullableProperty.Should(Be.EqualTo(3));
        }

        [Test]
        public void ShouldSetNullClassFields()
        {
            emptyInstance.Set("classField", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullInterfaceFields()
        {
            emptyInstance.Set("interfaceField", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullNullableFields()
        {
            emptyInstance.Set("nullableField", null);
            emptyInstance.NullableProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetNullObjectFields()
        {
            emptyInstance.Set("objectField", null);
            emptyInstance.ClassProperty.Should(Be.Null);
        }

        [Test]
        public void ShouldSetObjectFields()
        {
            var value = new List<int>();
            emptyInstance.Set("objectField", value);
            emptyInstance.ObjectProperty.Should(Be.EqualTo(value));
        }

        [Test]
        public void ShouldSetValueFields()
        {
            emptyInstance.Set("valueField", 1);
            emptyInstance.ValueProperty.Should(Be.EqualTo(1));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith1Parameter()
        {
            instance.Invoke("VoidMethod", 5);
            instance.VoidMethodCallResult.Should(Be.EqualTo("5"));
        }
        
        [Test]
        public void ShouldInvokeClassVoidMethodsWith2Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a");
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith3Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3);
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith4Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b");
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith5Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b", 4);
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b4"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith6Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b", 4,"c");
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b4c"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith7Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b", 4, "c", 5);
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b4c5"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith8Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b", 4, "c", 5, "d");
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b4c5d"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWith9Parameters()
        {
            instance.Invoke("VoidMethod", 1, "a", 3, "b", 4, "c", 5, "d", 6);
            instance.VoidMethodCallResult.Should(Be.EqualTo("1a3b4c5d6"));
        }

        [Test]
        public void ShouldInvokeClassVoidMethodsWithoutParameters()
        {
            instance.Invoke("VoidMethod");
            instance.VoidMethodCalled.Should(Be.True);
        }

        [Test]
        public void ShouldInvokeClassMethodsWithoutParameters()
        {
            instance.Invoke("GetMethod").Should(Be.EqualTo(4));
        }

        [Test]
        public void ShouldInvokeClassMethodsWith1Parameter()
        {
            instance.Invoke("GetMethod",1).Should(Be.EqualTo("1"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith2Parameters()
        {
            instance.Invoke("GetMethod", 1,"a").Should(Be.EqualTo("1a"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith3Parameters()
        {
            instance.Invoke("GetMethod", 1, "a",2).Should(Be.EqualTo("1a2"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith4Parameters()
        {
            instance.Invoke("GetMethod", 1, "a", 2,"b").Should(Be.EqualTo("1a2b"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith5Parameters()
        {
            instance.Invoke("GetMethod", 1, "a", 2, "b", 3).Should(Be.EqualTo("1a2b3"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith6Parameters()
        {
            instance.Invoke("GetMethod", 1, "a", 2, "b", 3, "c").Should(Be.EqualTo("1a2b3c"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith7Parameters()
        {
            instance.Invoke("GetMethod", 1, "a", 2, "b", 3, "c", 4).Should(Be.EqualTo("1a2b3c4"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith8Parameters()
        {
            instance.Invoke("GetMethod", 1, "a", 2, "b", 3, "c", 4, "d").Should(Be.EqualTo("1a2b3c4d"));
        }

        [Test]
        public void ShouldInvokeClassMethodsWithoutParametersWhenUsingGenericOverload()
        {
            instance.Invoke<int>("GetMethod").Should(Be.EqualTo(4));
        }

        [Test]
        public void ShouldInvokeClassMethodsWith1ParameterWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1).Should(Be.EqualTo("1"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith2ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a").Should(Be.EqualTo("1a"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith3ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2).Should(Be.EqualTo("1a2"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith4ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2, "b").Should(Be.EqualTo("1a2b"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith5ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2, "b", 3).Should(Be.EqualTo("1a2b3"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith6ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2, "b", 3, "c").Should(Be.EqualTo("1a2b3c"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith7ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2, "b", 3, "c", 4).Should(Be.EqualTo("1a2b3c4"));
        }
        [Test]
        public void ShouldInvokeClassMethodsWith8ParametersWhenUsingGenericOverload()
        {
            instance.Invoke<string>("GetMethod", 1, "a", 2, "b", 3, "c", 4, "d").Should(Be.EqualTo("1a2b3c4d"));
        }
 
        [Test]
        public void ShouldThrowIfGetMethodNotExists()
        {
            Assert.Throws<MemberNotFoundException>(() => instance.Invoke("XX"));
        }

        [Test]
        public void ShouldThrowIfVoidMethodNotExists()
        {
            Assert.Throws<MemberNotFoundException>(() => instance.Invoke("XX"));
        }

        [Test]
        public void ShouldThrowIfPropertyNotExistsWhenSetting()
        {
            Assert.Throws<MemberNotFoundException>(() => instance.Set("XX",null));
        }

        [Test]
        public void ShouldThrowIfPropertyNotExistsWhenGetting()
        {
            Assert.Throws<MemberNotFoundException>(() => instance.Get("XX"));
        }

        [Test]
        public void ShouldThrowIfFieldNotExistsWhenSetting()
        {
         Assert.Throws<MemberNotFoundException>(() => instance.Set("XX", null));
        }

        [Test]
        public void ShouldThrowIfFieldNotExistsWhenGetting()
        {
            Assert.Throws<MemberNotFoundException>(() => instance.Get("XX"));
        }

        [Test]
        public void ShouldRetrievePropertyFromChain()
        {
            instance.Get("Chain.ChainProperty").Should(Be.EqualTo(instance.Chain.ChainProperty));
        }

        [Test]
        public void ShouldRetrieveFieldFromChain()
        {
            instance.Get("Chain.chainField").Should(Be.EqualTo(instance.Chain.ChainProperty));
        }

        [Test]
        public void ShouldSetPropertyFromChain()
        {
            instance.Set("Chain.ChainProperty", "lalala");
            instance.Chain.ChainProperty.Should(Be.EqualTo("lalala"));
        }

        [Test]
        public void ShouldSetFieldFromChain()
        {
            instance.Set("Chain.chainField", "lalala");
            instance.Chain.ChainProperty.Should(Be.EqualTo("lalala"));
        }

        [Test]
        public void ShouldInvokeVoidMethodFromChain()
        {
            instance.Invoke("Chain.VoidMethod2", "test");
            instance.Chain.VoidMethodCalled.Should(Be.True);
        }

        [Test]
        public void ShouldInvokeMethodFromChain()
        {
            instance.Invoke("Chain.GetMethod2", 5).Should(Be.EqualTo(5));
        }

        [Test]
        public void ShouldInvokeMethodsWithNullableParameters()
        {
            instance.Invoke("VoidNullableMethod", 5);
            instance.VoidMethodCallResult.Should(Be.EqualTo("5"));
        }

        [Test]
        public void ShouldInvokeMethodsWithASubclassParameter()
        {
            instance.Invoke("VoidObjectMethod", "bla");
            instance.VoidMethodCallResult.Should(Be.EqualTo("bla"));
        }

        [Test]
        public void ShouldInvokeMethodsWithNullParameter()
        {
            instance.Invoke("VoidObjectMethod", NullValue.For<object>());
            instance.VoidMethodCallResult.Should(Be.EqualTo("null"));
        }

        [Test]
        public void ShouldInvokeMethodsWithNullableNullParameter()
        {
            instance.Invoke("VoidNullableMethod", NullValue.For<int?>());
            instance.VoidMethodCallResult.Should(Be.EqualTo("null"));
        }
    }
}